package serverSide;

import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import clientSide.UserInfo;
import common.Message;
import common.MessageType;
import common.TaskResult;

public class Server implements Runnable
{
	private Map<Integer, ServerThread> Clients;
	private Thread thread = null;
	private ServerForm form;
	///Server socket
	private ServerSocket socket;
	
	
	public Server(ServerForm form)
	{
		Clients = new HashMap<>();
		this.form = form;
		try
		{
			socket = new ServerSocket(Setting.Port);
			form.AddLog("Started server on port: " + Setting.Port);
			StartServer();
		}
		catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	@Override
	public void run() 
	{
		// TODO Auto-generated method stub
		while(thread != null)
		{
			try 
			{
				AddClient(socket.accept());
			}
			catch (IOException e) 
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
				break;
			}
		}
	}

	///Handle send and receive message
	public synchronized void HandleMessage(common.Message message) throws IOException
	{
		String content = new String(message.getContent(), "UTF-8");
		switch(message.getType())
		{
		case Login://request for login server
			String [] data = content.split(":");
			int id = Integer.parseInt(data[0]);
			ServerThread client = Clients.get(id);
			message.setSender("SERVER");
			if(CheckLogin(data[1], content.substring(data[0].length() + 1 + data[1].length() + 1 )))
			{
				//
				client.SetUserName(data[1]);
				message.setData("Login success".getBytes());
			}
			else
				{
					message.setData("Login fail".getBytes()) ;
					message.setTaskResult(TaskResult.Cancel);
				}
			client.SendMessage(message);
			//login
			break;
		case JoinChat://joint chat room
			SendMessage(new Message(message.getSender(), "All", MessageType.JoinChat, (message.getSender() + " join chat").getBytes()));
			break;
		case Logout:
			int idRemove = Integer.parseInt(new String(message.getContent(), "UTF-8").trim());
			RemoveClient(idRemove);
			break;
		case OutChat:
			break;
		case ReceiveFile:
			break;
		case Command:
			String []source = content.split(":");
			String cmd = "";
			if(source[0].trim().equalsIgnoreCase("Get"))
			{
				cmd = content.substring(4);
				if(cmd.equalsIgnoreCase("User"))
				{
						cmd = "SELECT Id, ImageSource, DisplayName, Age, Email  from USERS  where Id = '" + message.getSender() +  "'";
						try
						{
							List<Object[]> returndata = DataBaseTool.ConvertToList(DataBaseTool.ExcuteQuery(cmd));
							String ImageSource = returndata.get(0)[1].toString();
							String user_name, display_name, email;
							int age = Integer.parseInt(returndata.get(0)[3].toString().trim());
							user_name = returndata.get(0)[0].toString();
							display_name = returndata.get(0)[2].toString();
							email = returndata.get(0)[4].toString();
							UserInfo user = new UserInfo(user_name,display_name, email, age, ImageSource);
							Message msg = new Message("SERVER", message.getSender(), MessageType.CommandReturn, user.Serialize());
							msg.setHeader("UserInfo");
							SendMessage(msg);
						}
						catch (Exception e) 
						{
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
				}
				else
				{	
					cmd =  "select FRIEND_CATALOG.Name , FRIEND_LIST.Friend_Id  from FRIEND_LIST inner join FRIEND_CATALOG on FRIEND_LIST.Catalog_Id = FRIEND_CATALOG.Id and  U_Id = '" + message.getSender() +  "'";
					try
					{
						List<Object[]> returndata = DataBaseTool.ConvertToList(DataBaseTool.ExcuteQuery(cmd));
						String s = "";
						for(int i = 0; i < returndata.size(); i++)
						{
							s += returndata.get(i)[0].toString() + "@" + returndata.get(i)[1].toString() + "#";
						}
						if(s.length() > 0)
							s = s.substring(0, s.length() - "#".length());
						Message msg = new Message("SERVER", message.getSender(), MessageType.CommandReturn, s.getBytes());
						msg.setHeader("FriendList");
						SendMessage(msg);
					}
					catch (Exception e) 
					{
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				}
			}
			else
			{
				
			}
			
			break;
		case ResetPass:
			break;
		case SendFile:
			break;
		case SendMessage:
			SendMessage(message);
			break;
		default:
			SendMessage(new Message("SERER","ALL",MessageType.Test,"Received".getBytes()));
			break;
		}
	}
	///Add new instance of chat
	synchronized void AddClient(Socket socket)
	{
		try 
		{
			ServerThread client = new ServerThread(this, socket);
			client.Open();
			form.AddLog("Client on " + client.Id() + " Connected");
			Clients.put(client.Id(), client);
			client.SendMessage(new Message(client.getName(),"SERVER", MessageType.Test, "flush".getBytes()));
			client.start();			
			
		} 
		catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
	}

	
	///remove a client
	@SuppressWarnings("deprecation")
	synchronized void RemoveClient(int id)
	{
		if(Clients.containsKey(id))
		{
			try
			{
				Clients.get(id).Close();
			} 
			catch (IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			};
			Clients.get(id).stop();
			Clients.remove(id);
			form.AddLog("Client on " + id + " leave");
			try {
				SendMessage(new Message("SERVER", "All", MessageType.Logout,(id +  " logout").getBytes()));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
	
	public  void SendMessage(Message message) throws IOException
	{
		if(message.receiver == "All")
		{
			for(ServerThread c : Clients.values())
					c.SendMessage(message);	
			return;
		}
		for(ServerThread c : Clients.values())
			if( message.receiver.equalsIgnoreCase(c.getUserName()))
			//synchronized (c) {
				c.SendMessage(message);	
			//}
	}
	
	///Start run server
	public void StartServer()
	{
		if(thread == null)
			thread = new Thread(this);
		thread.start();
	}
	///stop server
	@SuppressWarnings("deprecation")
	public void StopServer()
	{
		if(Clients.size() != 0)
		{
			for(ServerThread i : Clients.values())
			{
				try
				{
					i.Close();
				} 
				catch (IOException e)
				{
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				i.stop();
			}
			Clients.clear();
		}
		
		if(thread != null)
			thread.stop();
		thread = null;
		if(socket != null)
		{
			try
			{
				socket.close();
			}
			catch (IOException e) 
			{
					// TODO Auto-generated catch block
					e.printStackTrace();
			}
		}
	}
		
	private boolean CheckLogin(String userName,String password)
	{
		DataBaseTool.Open("C_PLUS_PLUS\\SQLEXPRESS", "sa", "123456", "DoAnPhanBo", 1433);
		Param param = new Param(SqlDataType.Char, userName);//user
		param.Add(new ParamData(SqlDataType.Nvarchar,password));//pass
		try {
			List<Object[]> data = DataBaseTool.ConvertToList( DataBaseTool.CallProc("CheckUser",param));
			if(data.size() == 0)
				return false;
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return true;
	}
}


class ServerThread extends Thread
{
	private Socket client = null;
	private ObjectInputStream streamIn = null;
	private ObjectOutputStream streamOut = null;
	private int id;
	private String userName = null;
	private Server server = null;
	private List<Message> messages;
	public int Id()
	{
		return id;
	}
	
	public String getUserName()
	{
		return userName;
	}
	
	///Set UserName
	public void SetUserName(String user)
	{
		this.userName = user;
	}
	
	///constructor
	public ServerThread(Server server, Socket clientSocket)
	{
		this.server = server;
		this.client = clientSocket;
		id = client.getPort();
		messages = new ArrayList<>();
		userName = "user";
	}
	
	/// open connection
	public void Open() throws IOException
	{
		streamOut = new ObjectOutputStream(client.getOutputStream());
		streamOut.flush();
		streamIn = new ObjectInputStream(client.getInputStream());
	}
	
	public void Close() throws IOException
	{
		if(client != null)
			client.close();
		if(streamIn != null)
			streamIn.close();
		if(streamOut != null)
			streamOut.close();
	}
	
	@Override
	public void run() 
	{
		// TODO Auto-generated method stub
		while(true)
		{
			try 
			{
					Message msg = (Message)streamIn.readObject();
					messages.add(msg);
					Handle();
			}
			catch (ClassNotFoundException | IOException e)
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
				try 
				{
					Close();
					break;
					//System.exit(0);
				}
				catch (IOException e1) 
				{
					// TODO Auto-generated catch block
					e1.printStackTrace();
				}
				break;
			}
		}
	}
	
	private synchronized void Handle() throws IOException
	{
		if(messages.size() == 0)
			return;
		Message msg = messages.get(0);
		messages.remove(0);
		server.HandleMessage(msg);	
	}
	
	public synchronized void SendMessage(Message msg) throws IOException
	{
		//streamOut = new ObjectOutputStream(client.getOutputStream());
		if(streamOut == null)
			throw new NullPointerException("streamout is null");
		streamOut.writeObject(msg);
		streamOut.flush();
	}
	
	
}

